/*
 * Copyright (c) 2009 Nathaniel Houghton <nathan@brainwerk.org>
 *                    Robert Kirchgessner <rkirchge@gmail.com>
 *
 * Permission to use, copy, modify, and distribute this software for
 * any purpose with or without fee is hereby granted, provided that
 * the above copyright notice and this permission notice appear in all
 * copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
 * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
 * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
 * OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 *
 */

#include <stdio.h>
#include <string.h>

#include <avr/interrupt.h>
#include <util/delay.h>

#include "usart.h"
#include "config.h"
#include "event.h"
#include "util.h"
#include "nvm.h"
#include "elm.h"
#include "mode.h"
#include "ui.h"

static struct event event[MAX_EVENTS];
static int events_used;

uint8_t live_mode;

static volatile struct timer timer;

/* log delay in seconds */
#define LOG_DELAY 10
static uint8_t log_buf[38 * LOG_DELAY];
static uint16_t log_buf_used;
static uint32_t cur_nvm_addr;

#define FLASH_INTERVAL (LOG_DELAY * 2)

void
timer_start(void)
{
	usart_puts(0, "enabled timer\r\n");
	
	timer.ding = 0;
	timer.overflow = 0;
	
	TCCR1B = 0x00;

	TCNT1H = 0xFF;
	TCNT1L = 0xFF;
	TIFR1 = 0x01;
	TIMSK1 = 0x01;

	TCCR1B = 0x05;
}

void
timer_stop(void)
{
	TIMSK1 = 0x00;
	usart_puts(0, "disabled timer\r\n");
}

/* called at startup, and every time the config event's change */
int8_t
event_init(struct event *e)
{
	int i;

	events_used = 0;
	memset(event, 0, sizeof(event));

	for (i = 0; i < NUM_LOG_SLOTS; ++i) {
		if (event_add(e) == -1)
			return -1;
		++e;
	}

	return 0;
}

int8_t
event_add(struct event *e)
{
	if (events_used == 0) {
		event[0].interval = FLASH_INTERVAL;
		event[0].action = ACTION_FLASH;
		++events_used;
	}

	if (events_used >= MAX_EVENTS)
		return -1;
	if (e->action == ACTION_FLASH)
		return -1;

	memcpy(&event[events_used], e, sizeof(*e));
	event[events_used].count = 0;
	++events_used;

	return 0;
}

/* called to start / resume logging after event_init() */
void
events_start(uint32_t nvm_start_addr)
{
	int i;
	char buf[32];
	double mem_used;

	for (i = 0; i < events_used; ++i)
		event[i].count = 0;
	
	/*
	 * Leave  32 bit's of 1s here. Updated when logging is stopped.
	 * (points to start of next logging session).
	 */
	cur_nvm_addr = nvm_start_addr;
	cur_nvm_addr += 4;
	
	if (live_mode) {
		snprintf(buf, sizeof(buf), "  Live Statistics");
	} else {
		mem_used = (cur_nvm_addr * 100.0 / NVM_PAGE_SIZE) /
		    NVM_NUM_PAGES;
		snprintf(buf, sizeof(buf), "  Mem Used: %.2f%%",
		    mem_used);
	}
	lcd_update(&lcd, 1, buf);

	timer_start();
}

uint8_t
events_end(uint32_t *nvm_start_addr)
{
	char buf[64];
	uint8_t logged_something;

	logged_something = 0;

	nvm_write(cur_nvm_addr, log_buf, log_buf_used);
#if 0
	snprintf(buf, sizeof(buf), "wrote %d bytes to addr: "
	    "%08lx.\r\n", log_buf_used, cur_nvm_addr);
	usart_puts(0, buf);
#endif
	cur_nvm_addr += log_buf_used;
	log_buf_used = 0;

	/*
	 * Write ending address to the beginning of the data we just
	 * logged (if we logged anything). The +4 is the next pointer
	 * address we will write if we logged something.
	 */
	if (cur_nvm_addr > *nvm_start_addr + 4) {
		nvm_write(*nvm_start_addr, &cur_nvm_addr,
		    sizeof(cur_nvm_addr));
		snprintf(buf, sizeof(buf), "started at addr: %08lx, ended"
		    " at address: %08lx\r\n", *nvm_start_addr,
		    cur_nvm_addr);
		usart_puts(0, buf);
		*nvm_start_addr = cur_nvm_addr;
		logged_something = 1;
	} else
		usart_puts(0, "logged nothing\r\n");

	timer_stop();

	lcd_update(&lcd, 1, "");

	live_mode = 0;

	return logged_something;
}

void
events(struct mode *mode)
{
	int i;

	if (timer.overflow)
		fatal("timer overflowed...");

	if (!timer.ding)
		return;

	for (i = 0; i < events_used; ++i) {
		/* event disabled */
		if (event[i].interval == 0)
			continue;

		++event[i].count;
		if (event[i].count == event[i].interval) {
			/* perform some action */
			event[i].count = 0;
#if 0
			char buf[64];
			sprintf(buf, "event %d\r\n", i);
			usart_puts(0, buf);
#endif
			handle_event(&event[i], mode);
		}
	}

	timer.ding = 0;
}

void
handle_event(struct event *e, struct mode *mode)
{
	char buf[64];
	double mem_used;

	switch (e->action) {
	case ACTION_FLASH:
		nvm_write(cur_nvm_addr, log_buf, log_buf_used);
#if 0
		snprintf(buf, sizeof(buf), "wrote %d bytes to "
		    "addr: %08lx.\r\n", log_buf_used, cur_nvm_addr);
		usart_puts(0, buf);
#endif
		cur_nvm_addr += log_buf_used;
		log_buf_used = 0;

		if (live_mode) {
			snprintf(buf, sizeof(buf), "  Live Statistics");
		} else {
			mem_used = (cur_nvm_addr * 100.0 /
			    NVM_PAGE_SIZE) / NVM_NUM_PAGES;
			snprintf(buf, sizeof(buf), "  Mem Used: "
			    "%.2f%%", mem_used);
		}
		lcd_update(&lcd, 1, buf);
		break;

	case ACTION_ELM:
		elm_event(e, mode);
		break;
	}

#if 0
	snprintf(buf, sizeof(buf), "buf used: %d\r\n", log_buf_used);
	usart_puts(0, buf);
#endif
}

void
elm_event(struct event *e, struct mode *mode)
{
	uint8_t read; /*Number of bytes read from the ELM*/
	char buf[20];

	get_pid_error = 0;
	read = elm_getpid( e->pid, &log_buf[log_buf_used], sizeof(log_buf) - log_buf_used ); /*Read into the buffer*/
	
	if (get_pid_error)
	{
		change_mode(mode, MODE_CONFIG);
		snprintf(buf,sizeof(buf),"Get PID Error (%02x)", e->pid );
		lcd_update(&lcd, 1, buf);
		_delay_ms(500);
	}

//	usart_puts(0, "\r\n");
//	hexdump(&log_buf[log_buf_used], 2);
//	usart_puts(0, "\r\n");

	if (live_mode) {
		int i;
		uint32_t val = 0;
		uint8_t *p;

		p = (uint8_t *) &val;

		/* reverse byte order */
		for (i = 0; i < read && i < sizeof(val); ++i) {
			*p = log_buf[read - 1 - i];
			++p;
		}

		snprintf(buf, sizeof(buf), "%d=%ld\r\n", e->pid, val);
		usart_puts(0, buf);
	} else
		log_buf_used += read; /*Increment position counter*/
}

ISR(TIMER1_OVF_vect)
{
	TCNT1H = 0xF0;
	TCNT1L = 0xBE;

	if (timer.ding == 1)
		timer.overflow = 1;

	timer.ding = 1;
}
